﻿using NMF.Optimizations.Tests.BigPictureTests.My;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading.Tasks;

using NMF.Utilities;
using NMF.Optimizations.Linq;
using System.Diagnostics;


namespace NMF.Optimizations.Tests.BigPictureTests
{
    class ModelEvaluator : IScenarioEvaluator
    {
        private Model model;

        public ModelEvaluator(Model model)
        {
            this.model = model;
        }

        public double EvaluateScenario(IOptimizationScenario scenario)
        {
            return model.Propertys.Count * 5 + model.Entitys.Count;
        }
    }

    enum ClassDiagramRestructuringStrategy
    {
        EntityTuples,
        SingleEntities
    }

    class ClassDiagramRestructuring : Optimization<Model>
    {

        public ClassDiagramRestructuring(Model model, ClassDiagramRestructuringStrategy strategy) : base(model)
        {
            Entities = new ObservableSource<Entity>(model.Entitys);

            ForceModifications = true;

            switch (strategy)
            {
                case ClassDiagramRestructuringStrategy.EntityTuples:
                    AddPatternsEntityTuples();
                    break;
                case ClassDiagramRestructuringStrategy.SingleEntities: 
                    AddPatternsSingleEntity();
                    break;
                default:
                    break;
            }
        }

        private void AddPatternsEntityTuples()
        {
            AddPattern(from c1 in Entities
                       from c2 in Entities
                       where c1 != c2
                       && c1.Generalization.Select(g => g.General).FirstOrDefault() == c2.Generalization.Select(g => g.General).FirstOrDefault()
                       from a1 in c1.OwnedAttribute
                       where c2.OwnedAttribute.Any(p => p.Name == a1.Name && p.Type == a1.Type)
                       select new PullUpCommonAttributes(c1.Generalization.Select(g => g.General).FirstOrDefault(), a1, this));
        }

        private void AddPatternsSingleEntity()
        {

            AddPattern(from entity in Entities
                       where entity.Generalization.Count > 0
                       from baseEntity in entity.Generalization
                       from attribute in entity.OwnedAttribute
                       where AtLeastTwice(baseEntity
                           .General
                           .Specialization
                           .Select(g => g.Specific), attribute.Name, attribute.Type)
                       select new PullUpCommonAttributes(entity.Generalization.First().General, attribute, this));

            var withoutBase = from c1 in Entities
                              where c1.Generalization.Count == 0
                              select c1;

            AddPattern(from entity in withoutBase
                       from a in entity.OwnedAttribute
                       where AtLeastTwice(withoutBase, a.Name, a.Type)
                       select new PullUpCommonAttributes(null, a, this));
        }



        [DebuggerDisplay("Pull {Property.Name}:{Property.Type.Name} to {BaseEntity.Name}")]
        public class PullUpCommonAttributes : IOptimizationModification, IEquatable<PullUpCommonAttributes>
        {
            public Entity BaseEntity { get; private set; }
            public Property Property { get; private set; }
            public ClassDiagramRestructuring Parent { get; private set; }
            public Entity NewEntity { get; private set; }

            private List<Entity> Candidates { get; set; }

            public PullUpCommonAttributes(Entity baseEntity, Property property, ClassDiagramRestructuring parent)
            {
                if (parent == null) throw new ArgumentNullException("parent");
                if (property == null) throw new ArgumentNullException("property");

                Parent = parent;
                Property = new Property() { Name = property.Name, Type = property.Type };
                BaseEntity = baseEntity;
            }

            public IEnumerable<Entity> GetCandidates()
            {
                IEnumerable<Entity> initialCandidates;
                if (BaseEntity == null)
                {
                    initialCandidates = Parent.Model.Entitys.Where(box => box.Generalization.Count == 0);
                }
                else
                {
                    initialCandidates = BaseEntity.Specialization.Select(gen => gen.Specific);
                }

                return initialCandidates
                    .Where(e => e.OwnedAttribute.Any(p => p.Name == Property.Name && p.Type == Property.Type));
            }

            public bool Apply()
            {
                Candidates = GetCandidates().ToList();

                if (Candidates.Count <= 1)
                {
                    return false;
                }

                foreach (var candidate in Candidates)
                {
                    RemovePropertyFrom(candidate);
                }

                if (BaseEntity != null && Candidates.Count() == BaseEntity.Specialization.Count)
                {
                    BaseEntity.OwnedAttribute.Add(Property);
                    Parent.Model.Propertys.Add(Property);
                }
                else
                {
                    if (NewEntity == null) NewEntity = new Entity() { Name = Parent.GetNewClassName() };
                    NewEntity.OwnedAttribute.Add(Property);
                    Parent.Model.Propertys.Add(Property);
                    if (BaseEntity != null)
                    {
                        Parent.Model.Generalizations.Add(new Generalization() { General = BaseEntity, Specific = NewEntity });
                        foreach (var candidate in Candidates)
                        {
                            ChangeBaseEntity(candidate, BaseEntity, NewEntity);
                        }
                    }
                    else
                    {
                        foreach (var derived in Candidates)
                        {
                            var g = new Generalization()
                            {
                                General = NewEntity,
                                Specific = derived
                            };
                            Parent.Model.Generalizations.Add(g);
                        }
                    }
                    foreach (var candidate in Candidates)
                    {
                        Parent.Entities.Refresh(candidate);
                    }
                    Parent.Entities.Publish(NewEntity);
                }

                return true;
            }

            private void ChangeBaseEntity(Entity candidate, Entity oldBase, Entity newBase)
            {
                var generalization = candidate.Generalization.FirstOrDefault(g => g.General == oldBase);
                candidate.Generalization.Remove(generalization);
                generalization.General = null;
                Parent.Model.Generalizations.Remove(generalization);
                Parent.Model.Generalizations.Add(new Generalization() { General = newBase, Specific = candidate });
            }

            private void RemovePropertyFrom(Entity entity)
            {
                var prop = entity.OwnedAttribute.First(p => p.Name == Property.Name && p.Type == Property.Type);
                entity.OwnedAttribute.Remove(prop);
                Parent.Model.Propertys.Remove(prop);
            }

            public void Reverse()
            {
                if (Candidates.Count <= 1) return;

                if (NewEntity == null)
                {
                    RemovePropertyFrom(BaseEntity);
                    foreach (var candidate in Candidates)
                    {
                        AddPropertyClone(candidate);
                    }
                }
                else
                {
                    RemovePropertyFrom(NewEntity);
                    var g = NewEntity.Generalization.FirstOrDefault();
                    if (g != null)
                    {
                        g.General = null;
                        g.Specific = null;
                        Parent.Model.Generalizations.Remove(g);
                        foreach (var derived in Candidates)
                        {
                            ChangeBaseEntity(derived, NewEntity, BaseEntity);
                            Parent.Entities.Refresh(derived);
                            AddPropertyClone(derived);
                        }
                    }
                    else
                    {
                        foreach (var derived in Candidates)
                        {
                            var generalization = derived.Generalization
                                .Where(gen => gen.General == NewEntity).First();

                            generalization.General = null;
                            generalization.Specific = null;
                            Parent.Model.Generalizations.Remove(generalization);

                            Parent.Entities.Refresh(derived);
                            AddPropertyClone(derived);
                        }
                    }
                    Parent.Entities.Revoke(NewEntity);
                }
            }

            private void AddPropertyClone(Entity candidate)
            {
                var propertyClone = new Property()
                {
                    Name = Property.Name,
                    Type = Property.Type
                };
                candidate.OwnedAttribute.Add(propertyClone);
                Parent.Model.Propertys.Add(propertyClone);
            }

            public IEnumerable<object> Impact
            {
                get
                {
                    if (BaseEntity != null) yield return BaseEntity;
                    foreach (var candidate in GetCandidates())
                    {
                        yield return candidate;
                    }
                }
            }

            public override bool Equals(object obj)
            {
                return Equals(obj as PullUpCommonAttributes);
            }

            public override int GetHashCode()
            {
                var i = 0;
                if (BaseEntity != null) i ^= BaseEntity.GetHashCode();
                i ^= Property.Name.GetHashCode();
                i ^= Property.Type.GetHashCode();
                return i;
            }

            public bool Equals(PullUpCommonAttributes other)
            {
                if (other == null) return false;
                return other.BaseEntity == BaseEntity && other.Property.Name == Property.Name && other.Property.Type == Property.Type;
            }
        }

        private bool AtLeastTwice(IEnumerable<Entity> entities, string name, My.Type type)
        {
            return entities
                .Where(c => c.OwnedAttribute
                    .Any(p => p.Name == name && p.Type == type))
                .Count() > 1;
        }

        public ObservableSource<Entity> Entities { get; set; }

        private int classCounter = 0;
        internal string GetNewClassName()
        {
            classCounter++;
            return "NewClass" + classCounter.ToString();
        }
    }
}
